(function () {
    function esc(s) { return s.replace(/(')/g, '\\$1'); }
    function pad(s, len, c) { for (var p=len-s.length; p>0; p--) s=c+s; return s }
    function pad0(s) { return pad(s, 2, '0'); }
    var _formatter_cache = {};

    function formatter(s) {
        if (_formatter_cache.hasOwnProperty(s))
            return _formatter_cache[s];
        var ori_s = s;
        var res = [], c, idx=0, len;
        while ((c = s.match(/%.?/)) != null) {
            if ((idx = c.index) > 0)
                res.push(s.slice(0, idx));
            if ((len = c[0].length) != 2)
                throw "bad format";
            c = c[0].slice(1);
            res.push(c >= 'A' && c <= 'z' ?  {fmt: c} : c);
            s = s.slice(idx + len);
        }
        if (s.length)
            res.push(s);
        _formatter_cache[ori_s] = res;
        return res;
    }

    var _week_abr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    var _week = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    var _default_fmt = "%Y-%m-%d %H:%M:%S";
    var _fmt_getter = {
        Y: function() { return String(this.getFullYear()); },
        m: function() { return pad0(String(this.getMonth()+1)); },
        d: function() { return pad0(String(this.getDate())); },
        H: function() { return pad0(String(this.getHours())); },
        M: function() { return pad0(String(this.getMinutes())); },
        S: function() { return pad0(String(this.getSeconds())); },
        w: function() { return String(this.getDay()); },
        a: function() { return _week_abr[this.getDay()]; },
        A: function() { return _week[this.getDay()]; },
        y: function() { return String(this.getFullYear()).slice(2); },
        F: function() { var g = _fmt_getter; return g.Y.call(this)+'-'+g.m.call(this)+'-'+g.d.call(this); },
        X: function() { var g = _fmt_getter; return g.H.call(this)+':'+g.M.call(this)+':'+g.S.call(this); },
        undefined: function(x) { return x; }
    };
    var _fmt_setter = {
        Y: function(s) { this.setFullYear(s.match(/^\d{4}/)[0]); return s.slice(4); },
        m: function(s) { this.setMonth(parseInt(s.match(/^\d{2}/)[0], 10)-1); return s.slice(2); },
        d: function(s) { this.setDate(s.match(/^\d{2}/)[0]); return s.slice(2); },
        H: function(s) { this.setHours(s.match(/^\d{2}/)[0]); return s.slice(2); },
        M: function(s) { this.setMinutes(s.match(/^\d{2}/)[0]); return s.slice(2); },
        S: function(s) { this.setSeconds(s.match(/^\d{2}/)[0]); return s.slice(2); },
        undefined: function(s, x) { if(s.slice(0, x.length) != x) throw "bad format"; return s.slice(x.length); }
    };
    var _delta_act = {
        Y: function(d) { this.setFullYear(this.getFullYear()+d); },
        m: function(d) { this.setMonth(this.getMonth()+d); },
        d: function(d) { this.setDate(this.getDate()+d); },
        H: function(d) { this.setHours(this.getHours()+d); },
        M: function(d) { this.setMinutes(this.getMinutes()+d); },
        S: function(d) { this.setSeconds(this.getSeconds()+d); },
        bom: function(d) { while(d>0){this.setDate(32);--d;}while(d<0){this.setDate(0);++d;}this.setDate(1); },
        eom: function(d) { _delta_act.bom.call(this, d+1); this.setDate(0); },
        bod: function(d) { _delta_act.d.call(this, d); this.setHours(0, 0, 0); },
        eod: function(d) { _delta_act.d.call(this, d); this.setHours(23, 59, 59); }
    };

    Date.strptime = function(s, fmt) {
        fmt = fmt == undefined ? _default_fmt : fmt;
        var d = new Date(0);
        try {
            var fmt = formatter(fmt);
            for (var i=0,x=fmt[0]; i<fmt.length; x=fmt[++i])
                s = _fmt_setter[x.fmt].call(d, s, x);
            return d;
        } catch(err) {
            return null;
        }
    };

    Date.prototype.strftime = function(fmt) {
        fmt = fmt == undefined ? _default_fmt : fmt;
        var fmt = formatter(fmt), res = [];
        for (var i=0,x=fmt[0]; i<fmt.length; x=fmt[++i]) 
            res.push(_fmt_getter[x.fmt].call(this, x));
        return res.join("");
    };

    Date.prototype.addDelta = function(delta) {
        var d = new Date(this.getTime());
        for (var x in delta)
            _delta_act[x].call(d, delta[x]);
        return d;
    };

    Date.prototype.gt = function(another) { return this.getTime() > another.getTime(); };
    Date.prototype.ge = function(another) { return this.getTime() >= another.getTime(); }; 
    Date.prototype.lt = function(another) { return this.getTime() < another.getTime(); }; 
    Date.prototype.le = function(another) { return this.getTime() <= another.getTime(); }; 

})();

// test cases:
// alert("test strftime and strptime: ");
// var d= new Date();
// alert(d.strftime());
// d.strptime(d.strftime());
// alert(d.strftime());
// 
// alert("test bom eom: ");
// alert(d.addDelta({bom: -5}).strftime());
// alert(d.addDelta({eom: -5}).strftime());
// 
// alert("test bod eod: ");
// alert(d.addDelta({bod: 4}).strftime());
// alert(d.addDelta({eod: 4}).strftime());
// 
// alert("test mix: ");
// alert(d.addDelta({bom: -5, bod: 0}).strftime());
// alert(d.addDelta({eom: -5, eod: 0}).strftime());
